/***
 * * PROJECT
 * *    Logical Neighborhoods - J2ME Run-Time Support 
 * * VERSION
 * *    $LastChangedRevision: 118 $
 * * DATE
 * *    $LastChangedDate: 2008-05-28 19:35:12 +0200 (Wed, 28 May 2008) $
 * * LAST_CHANGE_BY
 * *    $LastChangedBy: lmottola $
 * *
 * *    $Id: SSD.java 118 2008-05-28 17:35:12Z lmottola $
 * *
 * *   Logical Neighborhoods - 
 * *                   Programming Wireless Sensor Networks
 * *                   (One Slice at a Time)
 * *
 * *   This program is free software; you can redistribute it and/or
 * *   modify it under the terms of the GNU Lesser General Public License
 * *   as published by the Free Software Foundation; either version 2.1
 * *   of the License, or (at your option) any later version.
 * *
 * *   This program is distributed in the hope that it will be useful,
 * *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 * *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * *   GNU General Public License for more details.
 * *
 * *   You should have received a copy of the GNU Lesser General Public 
 * *   License along with this program; if not, you may find a copy at 
 * *   the FSF web site at 'www.gnu.org' or 'www.fsf.org', or you may 
 * *   write to the Free Software Foundation, Inc., 59 Temple 
 * *   Place - Suite 330, Boston, MA  02111-1307, USA
 ***/

package atag.runtime.ln;

import java.util.Vector;

import java.util.logging.Level;
import java.util.logging.Logger;
import atag.runtime.ln.neighborhoodDefs.ConjunctiveNeighborhood;
import atag.runtime.ln.neighborhoodDefs.Predicate;
import atag.runtime.ln.nodeAttributes.Node;
import atag.runtime.ln.nodeAttributes.NodeAttribute;
import atag.runtime.ln.SSD.SSDAttribute;
import atag.runtime.ln.messages.ProfAdv;

/**
 * @author Luca Mottola <a
 *         href="mailto:mottola@elet.polimi.it">mottola@elet.polimi.it</a>
 * 
 */
public class SSD {
   //change this to a suitable value
    private static final int MAX_SSD = 20;

	private static long idEntryCounter = 0;

	private Node nodeInfo;

	private LogicalNeighborhoods ln;

    // make this private
	public Vector ssdData;

    //private Timer timer;

	public SSD(Node nodeInfo, LogicalNeighborhoods ln) {

		this.nodeInfo = nodeInfo;
		this.ln = ln;
		this.ssdData = new Vector();
        //this.timer = new Timer();
        //Thread t = new Thread(timer);
        //t.start();

        /*new Thread(new Runnable() {

			public void run() {
				while (true)
                {
                    try {
                        Thread.sleep(10000);
                        //pruneExpiredEntries();
                    } catch (InterruptedException ex) {
                        Logger.getLogger(SSD.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
			}
		}).start();*/
	}

	protected void parseProfAdv(ProfAdv adv, long ssdTime) {

		// TODO: Add links between entries to compress State Space
		boolean modified = false;
		adv.setCost(adv.getCost() + nodeInfo.getSendingCost());

		for (int i = 0; i < adv.getNodeAttributes().size(); i++) {
			NodeAttribute profAdvAttr = (NodeAttribute) adv.getNodeAttributes()
					.elementAt(i);
            boolean found = false;
            for (int j = 0; j < ssdData.size(); j++) {
                SSDAttribute ssdAttr = (SSDAttribute) ssdData.elementAt(j);
                if (ssdAttr.attribute.equals(profAdvAttr))
                {
                    found = true;
                   if(adv.getCost() < ssdAttr.cost)
                   {
                     modified = true;
                     ssdAttr.cost = adv.getCost();
                     ssdAttr.setDecreasingPath(adv.getSenderId());
                     ssdAttr.setOriginatorId(adv.getOriginatorId());
                     modified = true;
                   }
                   else if    (ssdAttr.originatorId == adv.getOriginatorId()
                            && ssdAttr.decreasingPath == adv.getSenderId()
                            && ssdAttr.cost == adv.getCost())
                   {
                        ssdAttr.expirationTime = ssdTime + LogicalNeighborhoods.EXPIRATION_TICKS;
                        modified = true;
                   }
                   break;
                }
            }
            if(!found)
            {
                ssdData.addElement(new SSDAttribute(idEntryCounter++,
						profAdvAttr, adv.getCost(), adv.getOriginatorId(),
						ssdTime,adv.getSenderId()));
                modified = true;
            }
			/*if (needsInsertion(profAdvAttr, adv.getCost())) {
				modified = true;
				ssdData.addElement(new SSDAttribute(idEntryCounter++,
						profAdvAttr, adv.getCost(), adv.getOriginatorId(),
						ssdTime,adv.getSenderId()));
			}*/
		}
		if (modified) {
            this.updateAssociations(adv);
            removeStaleAssociations(adv);
            
			nodeInfo.debugPrint("Propagating node profile for "
					+ adv.getOriginatorId() + " received from "
					+ adv.getSenderId());
             	ln.forwardMessage(adv);
		}
        //this.printSSD();
	}

	public void pruneExpiredEntries(long ssdTime) {
        for (int i = 0; i < this.ssdData.size(); i++)
        {
            SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);
            if (attr.expirationTime < ssdTime)
            {
                attr = (SSDAttribute)this.ssdData.elementAt(this.ssdData.size()-1);
                this.ssdData.setElementAt(attr,i);
                this.ssdData.removeElementAt(this.ssdData.size()-1);
            
             // Removing stale associations
             for (int k = 0; k < this.ssdData.size(); k++)
             {
                SSDAttribute attr1 = (SSDAttribute)this.ssdData.elementAt(k);
                for (int j = 0; j < attr1.associactions.size(); j++)
                {
                    int assIndex = ((Integer)attr1.associactions.elementAt(j)).intValue();
                    if (assIndex == i)
                    {
                        Object o = attr1.associactions.elementAt(j);
                         o = attr1.associactions.elementAt(attr1.associactions.size()-1);
                         attr1.associactions.setElementAt(o, j);
                         attr1.associactions.removeElementAt(attr1.associactions.size()-1);
                    }
                }
             }

            // Update associations to the moved element
             for (int k = 0; k < this.ssdData.size(); k++)
             {
                SSDAttribute attr2 = (SSDAttribute)this.ssdData.elementAt(k);
                for (int j = 0; j < attr2.associactions.size(); j++)
                {
                    int assIndex = ((Integer)attr2.associactions.elementAt(j)).intValue();
                    if (assIndex == this.ssdData.size())
                    {
                        attr2.associactions.setElementAt(new Integer(i), j);
                    }
                }
             }
            }
        }

	}

	protected void disseminateProfAdv(long ssdTime) {
		Vector disseminate = new Vector();
		for (int i = 0; i < nodeInfo.getNodeAttributes().size(); i++) {
			disseminate.addElement(nodeInfo.getNodeAttributes().elementAt(i));
		}
		ProfAdv profAdv = new ProfAdv(nodeInfo.getMyId(),ssdTime, disseminate);
		nodeInfo.debugPrint("Disseminating node profile at time " + ssdTime);
        ln.enqueueMessage(profAdv);
	}

	/*private boolean updateSSD(NodeAttribute attribute, int cost) {

		for (int i = 0; i < ssdData.size(); i++) {
			SSDAttribute ssdAttr = (SSDAttribute) ssdData.elementAt(i);
			if (ssdAttr.attribute.equals(attribute) && ssdAttr.cost < cost) {
				return false;
			}
            else
            {
                ssdAttr.setDecreasingPath(i);
            }
		}
		return true;
	}*/

    private void updateAssociations(ProfAdv adv)
    {
        int from = -1,to = -1;
        for (int i = 0; i < this.ssdData.size(); i++)
        {
            from = -1;
            to = -1;
            ((SSDAttribute)this.ssdData.elementAt(i)).associationsupdated = false;
            if((from = isInMsg((SSDAttribute)this.ssdData.elementAt(i),adv)) >= 0 )
            {
                to = -1;
                SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);

               for (int j = 0; j < this.ssdData.size(); j++)
               {
                   
                   //System.out.println(i+"  "+j);
                   if((to = isInMsg((SSDAttribute)this.ssdData.elementAt(j),adv)) >= 0
                       && from != to
                       && ((SSDAttribute)this.ssdData.elementAt(i)).cost >= ((SSDAttribute)this.ssdData.elementAt(j)).cost
                       && !isAssociated(i,j)
                       && i != j)
                   {
                        attr.associactions.add(new Integer(j));
                        attr.associationsupdated = true;
                   }
               }
            }
        }
    }
    private int isInMsg(SSDAttribute entry, ProfAdv adv)
    {
        for (int i = 0; i < adv.getNodeAttributes().size(); i++)
        {
            NodeAttribute attr = (NodeAttribute)adv.getNodeAttributes().elementAt(i);
            boolean b = entry.attribute.equals(attr);
            if (b == true);
            {
                if(b == true)
                {
                 //System.out.println("value"+b);
                 return i ;
                }
            }
        }
        return -1;
    }

    private boolean isAssociated(int entryId, int associatedId)
    {
        SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(entryId);
        for (int i = 0; i < attr.associactions.size();i++)
        {
           if (attr.associactions.elementAt(i) == new Integer(associatedId))
           {
               return true ;
           }
        }
        return false;
    }
    
    private void removeStaleAssociations(ProfAdv adv)
    {
        for (int i = 0; i < this.ssdData.size(); i++)
        {
            SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);
            for (int j = 0; j < attr.associactions.size(); j++)
            {
                //May be there is a bug in this if block
                if(attr.associationsupdated && attr.associactions.size() >= adv.getNodeAttributes().size())
                {
                    for (int k = 0; k <=  attr.associactions.size() - adv.getNodeAttributes().size(); k++)
                    {
                        attr.associactions.remove(k);
                    }
                }
                SSDAttribute assocAttr  =   (SSDAttribute)this.ssdData.elementAt(((Integer)attr.associactions.elementAt(j)).intValue());
                if (assocAttr.cost > attr.cost)
                {
                    attr.associactions.remove(j);
                }
            }
        }
    }

    /**
 * Mactches Neighborhood definition with a pivot entry
 * I will define pivot entry
 * argument is a specifier that is relative to the url argument.
 *
 * @param  cn  Conjunctive neighborhood of the target data
 * @return      the pivot entry
 */

    public int SSDMatches(ConjunctiveNeighborhood cn)
    {
      int pivotValue = 0;
      boolean pivotFound = false;
      //Find the cost of any pivot value
      for (int i = 0; i < this.ssdData.size(); i++)
      {
          SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);
          for (int k = 0; k < cn.getPredicates().size(); k++)
          {
              Predicate p = (Predicate)cn.getPredicates().elementAt(k);
              if (p.matches(attr.attribute) && attr.cost > pivotValue)
              {
                pivotValue = attr.cost;
                pivotFound = true;
              }
          }
      }
      //No pivot found
      if (!pivotFound)
      {
          return -1 ;
      }
      //Find candidate pivots
      Vector pivotCandidates = new Vector();
      Vector pivotCandidates_ln = new Vector();
      
      for (int i = 0; i < this.ssdData.size(); i++)
      {
          SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);
          for (int k = 0; k < cn.getPredicates().size(); k++)
          {
              Predicate p = (Predicate)cn.getPredicates().elementAt(k);
              if (p.matches(attr.attribute) && attr.cost == pivotValue)
              {
                  pivotFound = true;
                  pivotCandidates.addElement(new Integer(i));
                  pivotCandidates_ln.addElement(new Integer(k));
              }
          }
      }
      int j;
      // Check associations from pivot
      boolean associations_check = false;
      for (j = 0; j < pivotCandidates.size(); j++)
      {
          SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt( ((Integer)pivotCandidates.elementAt(j)).intValue() );
          for (int k = 0; k < cn.getPredicates().size(); k++)
          {
              associations_check = true;
              int cnid = ((Integer)pivotCandidates_ln.elementAt(j)).intValue();
              if (k != cnid)
              {
                  associations_check = false;
                  for (int i =0; i < attr.associactions.size(); i++)
                  {
                      int assoIndex = ((Integer)attr.associactions.elementAt(i)).intValue();
                      SSDAttribute assoattr = (SSDAttribute)this.ssdData.elementAt(assoIndex);
                      Predicate p = (Predicate)cn.getPredicates().elementAt(k);
                      if (p.matches(assoattr.attribute))
                      {
                        associations_check = true;
                      }
                  }
                  if (!associations_check)
                  {
                      break;
                  }
              }
          }
          if (!associations_check)
          {
              continue;
          }
          else
          {
              break;
          }
      }

      try{
      Integer ret = (Integer)pivotCandidates.elementAt(j);
      return ret.intValue();
      }
      catch (java.lang.ArrayIndexOutOfBoundsException e)
      {
          return -1;
      }
    }

    public void printSSD()
    {
        System.out.println("############################SSDINFO##################################");
        System.out.println("entryID           "+
                           "cost              "+
                           "decreasingPath    "+
                           "originator ID     "+
                           "expirationTime    "+
                           "associations      "+
                           "attribute         ");
        for (int i = 0; i < this.ssdData.size(); i++ )
        {
            SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);
            attr.printSSD();
        }

        return ;
    }

    /*To be tested rigorously*/
    private void insertReverseLink(ProfAdv adv)
    {
        boolean refreshed = false;

        for (int i = 0; i < this.ssdData.size(); i++)
        {
            SSDAttribute attr = (SSDAttribute)this.ssdData.elementAt(i);
            int id = this.isInMsg(attr, adv);
            // this is a change away from nesc( != instead of == in nesc)
            if (id >= 0 && attr.originatorId != adv.getOriginatorId())
            {
                for (int j = 0; j < attr.reverseLinks.size(); j++)
                {
                    refreshed = false;
                    int link = ((Integer)attr.reverseLinks.elementAt(j)).intValue();
                    if (link == adv.getSenderId())
                    {
                        Integer l = (Integer)attr.reverseLinksexpiration.elementAt(j);
                        int dum = l.intValue() + 10;
                        attr.reverseLinksexpiration.setElementAt(new Integer(dum), j);

                        refreshed = true;
                        break;
                    }
                }
                if (!refreshed)
                {
                    attr.reverseLinks.addElement(new Integer(adv.getSenderId()));
                    // a suitable expiration to be set
                    attr.reverseLinksexpiration.addElement(new Integer(10));
                }
            }
        }
    }

	class SSDAttribute {

		private long entryId;

		private NodeAttribute attribute;

		private int cost;

        // make this private
		public int decreasingPath;

		private Vector reverseLinks;

        private Vector reverseLinksexpiration;

		private int originatorId;

		private long expirationTime;

        private Vector associactions;

        private boolean associationsupdated;

		public SSDAttribute(long entryId, NodeAttribute attr, int cost,
				int originatorId, long ssdTime, int decreasingPath) {

			this.entryId = entryId;
			this.attribute = attr;
			this.cost = cost;
			this.originatorId = originatorId;
			this.expirationTime = ssdTime+
					+ LogicalNeighborhoods.EXPIRATION_TICKS;
            this.decreasingPath = decreasingPath;
            this.associactions = new Vector();
		}

		protected void update(long ssdTime) {
			this.expirationTime = ssdTime
					+ LogicalNeighborhoods.EXPIRATION_TICKS;
		}

        public void setDecreasingPath(int id)
        {
            this.decreasingPath = id;
        }

        public void setOriginatorId(int id)
        {
            this.originatorId = id;
        }

        public void printSSD()
        {
            System.out.println(this.entryId+"    "+
                              this.cost+"    "+
                              this.decreasingPath+"    "+
                              this.originatorId+"    "+
                              this.expirationTime+"     "+
                              this.associactions+
                              this.attribute.toString()+"    ");
        }
	}
}
